home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-08-18 | 9.7 KB | 327 lines | [TEXT/R*ch] |
- (* Help -- a simple Moscow ML library browser, PS 1995-04-30, 1995-11-20
-
- Using a signature index database,
-
- Uses argv_ to get the library directory, then reads and displays
- (signature) files from that directory.
-
- The search facility cyclically searches for occurrences of a given
- string, and displays the line in which the string was found, as close
- to the center of the display (or portion displayed) as possible.
-
- *)
-
- (* The number of lines to show interactively: *)
-
- val displayLines = ref 24
-
- local
- fun print s = TextIO.print s
-
- (* Name of the signature index database, must reside in the std library: *)
-
- val dbfilename = "helpsigs.val"
-
- (* The database reading and searching functions. Included here only
- * to avoid loading yet another structure. Types MUST agree with unit
- * Database in mosml/src/toolssrc. *)
-
- datatype component =
- Str (* structure *)
- | Exc of string (* exception constructor with name *)
- | Typ of string (* type constructor with name *)
- | Val of string (* value with name *)
- | Con of string (* value constructor with name *)
-
- type entry = { comp : component, str : string, line : int }
-
- datatype 'contents table =
- Empty
- | Node of string * 'contents * 'contents table * 'contents table
-
- type database = entry list table
-
- fun readbase filename =
- let prim_type in_channel
- type instream_ = { closed: bool, ic: in_channel } ref
- prim_val input_value_ : in_channel -> 'a = 1 "intern_val"
- prim_val fromI : BasicIO.instream -> instream_ = 1 "identity"
- fun input_value is =
- let val ref {closed, ic} = fromI is in
- if closed then
- raise SysErr("Input stream is closed", NONE)
- else
- input_value_ ic
- end
- val is = BasicIO.open_in_bin filename
- val db = input_value is : database
- in BasicIO.close_in is; db end
-
- fun lookup(db : database, sought : string) =
- let fun look Empty = []
- | look (Node(key, value, t1, t2)) =
- if sought < key then look t1
- else if key < sought then look t2
- else value
- in look db end
-
- (* The global variable holding the database after the first use: *)
-
- val dbOpt = ref NONE : database option ref;
-
- (* Auxiliaries: *)
-
- fun min (x, y) = if x < y then x else y : int;
- fun max (x, y) = if x < y then y else x : int;
-
- fun natFromString s =
- let fun skipWS [] = []
- | skipWS (cs as (c::cr)) = if Char.isSpace c then skipWS cr else cs
- fun decval c = Char.ord c - 48
- fun h [] res = SOME res
- | h (c::cr) res = if Char.isDigit c then h cr (decval c + 10 * res)
- else SOME res
- in
- case skipWS (String.explode s) of
- [] => NONE
- | c::cr => if Char.isDigit c then h cr (decval c)
- else NONE
- end
-
- fun natToString n =
- (if n > 9 then natToString (n div 10) else "")
- ^ String.str (Char.chr(48 + n mod 10))
-
- fun normalize [] = []
- | normalize (#"\n" :: _) = []
- | normalize (c :: cr) = Char.toLower c :: normalize cr
-
- fun toLower s = String.implode (normalize (String.explode s))
-
- #ifdef macintosh
- val slash = #":"
- #else (* DOS/UNIX *)
- val slash = #"/"
- #endif
-
- fun joinDirFile dir file =
- let open String
- in
- if dir <> "" andalso sub(dir, size dir - 1) = slash then
- dir ^ file
- else
- dir ^ str slash ^ file
- end
-
- (* The signature browser: *)
-
- fun show name centerline initiallySought (strs : string Vector.vector) =
- let prim_val sub_ : string -> int -> char = 2 "get_nth_char";
- prim_val int_to_string : int -> string = 1 "sml_string_of_int";
-
- val lines = Vector.length strs
- val sought = ref initiallySought
- fun instr s str =
- let val len = String.size s
- fun eq j k =
- j >= len orelse
- sub_ s j = Char.toLower (sub_ str k) andalso eq (j+1) (k+1)
- val stop = String.size str - len
- fun cmp k = k<=stop andalso (eq 0 k orelse cmp(k+1))
- in cmp 0 end;
- fun occurshere str =
- case !sought of
- NONE => false
- | SOME s => instr s str
- fun findline s curr =
- let fun h i =
- if i >= lines then NONE
- else if instr s (Vector.sub(strs, (i+curr) mod lines)) then
- SOME ((i + curr) mod lines)
- else h(i+1)
- in h 0 end
- val portion = max(!displayLines, 5) - 1
- fun wait next =
- let val prompt =
- "---- " ^ name ^ "[" ^
- int_to_string((100 * next) div lines)
- ^ "%]: down, up, bottom, top, /(find), next, quit: "
- fun toend () = (print "\n....\n";
- nextpart (lines - portion) portion)
- fun tobeg () = (print "\n....\n"; nextpart 0 portion)
- fun up () = (print "\n....\n";
- nextpart (next-3*portion div 2) portion)
- fun down () = if next=lines then toend()
- else nextpart next (portion div 2)
- fun find s =
- case findline s next of
- NONE =>
- (print ("**** String \"" ^ s ^ "\" not found\n");
- wait next)
- | SOME line =>
- (print "\n....\n";
- nextpart (line - portion div 2) portion)
- fun search chars =
- let val s = String.implode (normalize chars)
- in sought := SOME s; find s end
- fun findnext () =
- (case !sought of
- NONE => (print "**** No previous search string\n";
- wait next)
- | SOME s => find s)
- in
- print prompt;
- case String.explode(BasicIO.input_line BasicIO.std_in) of
- [] => ()
- | #"q" :: _ => ()
- | #"u" :: _ => up ()
- | #"d" :: _ => down ()
- | #"t" :: _ => tobeg ()
- | #"g" :: _ => tobeg ()
- | #"b" :: _ => toend ()
- | #"G" :: _ => toend ()
- | #"/" :: s => search s
- | #"n" :: s => findnext ()
- | _ => if next=lines then toend ()
- else nextpart next portion
- end
- and nextpart first amount =
- let val start = max(0, min(lines - amount + 1, first))
- val stop = min(start + amount, lines)
- in prt wait start stop end
- and prt wait i stop =
- if i >= stop then wait i
- else
- let val line = Vector.sub(strs, i)
- in
- if occurshere line then print "@>" else print "+ ";
- print line;
- prt wait (i+1) stop
- end
- in
- print "\n";
- if lines <= portion then prt ignore 0 lines
- else nextpart (centerline - portion div 2) portion
- end
-
- (* Find the standard library directory: *)
-
- fun getstdlib () =
- let open Vector
- prim_val argv_ : string vector = 0 "command_line";
- val stop = length argv_ - 1;
- fun h i =
- if i < stop then
- if sub(argv_, i) = "-stdlib" then sub(argv_, i+1)
- else h (i+1)
- else
- raise Fail "Cannot find the standard libraries!"
- in h 0 end;
-
- (* Read a signature file from the standard library: *)
-
- fun readfile file =
- let val is = BasicIO.open_in (joinDirFile (getstdlib ()) file)
- fun h () = if BasicIO.end_of_stream is then []
- else BasicIO.input_line is :: h ()
- in Vector.fromList (h ()) end;
-
- (* Invoke the browser on a particular line of a signature: *)
-
- fun showFile sought entry =
- (case entry of
- {comp = Str, str, ...} =>
- show str 0 NONE (readfile (str ^ ".sig"))
- | {comp, str, line} =>
- show str line (SOME sought) (readfile (str ^ ".sig")))
- handle SysErr _ => raise Fail "Help.showFile: inconsistent help database"
-
- (* Let the user select from the menu: *)
-
- fun choose sought entries =
- let val _ = print "\nChoose number to browse, or quit: ";
- val response = BasicIO.input_line BasicIO.std_in
- in
- case natFromString response of
- NONE => (case String.explode response of
- [] => ()
- | [#"\n"] => ()
- | #"Q" :: _ => ()
- | #"q" :: _ => ()
- | _ => choose sought entries)
- | SOME choice =>
- if choice = 0 then ()
- else showFile sought (List.nth(entries, choice - 1))
- end
- handle Subscript => choose sought entries
- | Overflow => choose sought entries;
-
- (* Display the menu of identifiers matching the given one, or
- * invoke the browser directly if only one match:
- *)
-
- fun display sought [] = raise Fail "Help.display"
- | display sought [entry] = showFile sought entry
- | display sought (entries as e0::er) =
- let fun render (entry as {comp, str, ...}) =
- case comp of
- Str => "structure " ^ str
- | Exc id => "exn " ^ str ^ "." ^ id
- | Typ id => "type " ^ str ^ "." ^ id
- | Val id => "val " ^ str ^ "." ^ id
- | Con id => "con " ^ str ^ "." ^ id
- fun maxlen [] max = max
- | maxlen (e1 :: er) max =
- let val len = size (render e1)
- in maxlen er (if len > max then len else max) end
- val maxwidth = maxlen er (size (render e0))
- val boxwidth = 6 + 3 + 3 + maxwidth + 2
- val horizontal = StringCvt.padRight #"-" boxwidth " " ^ "\n"
-
- fun prline lin [] = ()
- | prline lin (e1 :: rest) =
- (print " | ";
- print (StringCvt.padLeft #" " 3 (natToString lin));
- print " | ";
- print (StringCvt.padRight #" " maxwidth (render e1));
- print " |\n";
- prline (lin+1) rest)
- in
- print "\n";
- print horizontal;
- prline 1 entries;
- print horizontal;
- choose sought entries
- end
-
- in
-
- (* Main help function: search for a string in the signature index database: *)
-
- fun help "" =
- show "help" 0 NONE
- #["Moscow ML library browser: \n",
- "\n",
- " help \"lib\"; gives an overview of the library units\n",
- " help \"id\"; provides help on identifier id\n",
- "\n"]
- | help "lib" = show "Overview" 0 NONE (readfile "README")
- | help "README" = show "README" 0 NONE (readfile "README")
- | help id =
- let fun getdb filename =
- case !dbOpt of
- SOME db => db
- | NONE =>
- let val db = readbase (joinDirFile (getstdlib ()) filename)
- in dbOpt := SOME db; db end
- handle SysErr _ => raise Fail "Cannot read help database!"
- val db = getdb dbfilename
- val sought = toLower id
- val entries = lookup(db, sought)
- in
- case entries of
- [] => print ("\nSorry, no help on identifier `" ^ id ^ "'\n\n")
- | _ => display sought entries
- end
- end
-